package org.budo.sharding.config;

import java.util.Map;

import org.budo.apache.sharding.datasource.ApacheShardingMasterSlaveDataSourceFactoryBean;
import org.budo.apache.sharding.hint.aop.ApacheShardingAnnotationRoutingDataSourceAopInterceptor;
import org.budo.sharding.datasource.MasterSlaveDataSourceFactoryBean;
import org.budo.sharding.hint.aop.AnnotationRoutingDataSourceAopInterceptor;
import org.budo.support.lang.util.MapUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.spring.aop.advisor.BudoAopPointcutAdvisor;
import org.budo.support.spring.bean.factory.support.BeanBuilder;
import org.budo.support.spring.util.SpringUtil;
import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.util.ObjectUtils;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@ToString
@Slf4j
@Getter
@Setter
public class BudoShardingConfigurer implements BeanDefinitionRegistryPostProcessor, BeanNameAware {
    private static final Boolean APACHE_SHARDING_SPHERE = ReflectUtil.hasClass("org.apache.shardingsphere.api.hint.HintManager");

    private String masterSlaveDataSourceBeanName = "dataSource";

    private Boolean primary;

    private String masterDataSourceName;

    private String aopPatterns;

    private Map<String, Integer> slaveDataSourceNameMap;

    private String slaveDataSourceName;

    private Boolean defaultMasterOnly = false;

    @Override
    public void setBeanName(String name) {
        log.info("#50 on setBeanName, name=" + name + ", this=" + this);

        if (null != name && !name.contains("#")) { // budo.Sharding#0
            this.setMasterSlaveDataSourceBeanName(name);
        }
    }

    public void setId(String id) {
        log.info("#50 on setId, id=" + id + ", this=" + this);

        if (null != id && !id.contains("#")) { // budo.Sharding#0
            this.setMasterSlaveDataSourceBeanName(id);
        }
    }

    private Map<String, Integer> slaveDataSourceNameMap() {
        if (MapUtil.isNullOrEmpty(this.slaveDataSourceNameMap) && StringUtil.isNullOrEmpty(this.slaveDataSourceName)) {
            throw new RuntimeException("#45 this=" + this); // 两个都空
        }

        if (!MapUtil.isNullOrEmpty(this.slaveDataSourceNameMap) && !StringUtil.isNullOrEmpty(this.slaveDataSourceName)) {
            throw new RuntimeException("#51 this=" + this); // 两个都不空
        }

        if (!MapUtil.isNullOrEmpty(this.slaveDataSourceNameMap)) {
            return this.slaveDataSourceNameMap;
        }

        return MapUtil.newMap(this.slaveDataSourceName, 1);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        log.info("#17 postProcessBeanFactory, beanFactory=" + ObjectUtils.identityToString(beanFactory));
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        log.info("#22 postProcessBeanDefinitionRegistry, registry=" + ObjectUtils.identityToString(registry));

        this.registerShardingAopInterceptor(registry);
        this.registryShardingAopAdvisor(registry);

        this.registerMasterSlaveDataSource(registry);
    }

    private void registerMasterSlaveDataSource(BeanDefinitionRegistry registry) {
        if (StringUtil.isBlank(this.getMasterDataSourceName())) {
            throw new IllegalArgumentException("#57 masterDataSourceName is null, masterDataSourceName=" + this.getMasterDataSourceName() + ", this=" + this);
        }

        Class<?> type = this.masterSlaveDataSourceFactoryBeanType();
        String id = this.getMasterSlaveDataSourceBeanName();

        log.info("#92 registerMasterSlaveDataSource, masterSlaveDataSourceBeanName = " + id + ", this=" + this);

        new BeanBuilder() //
                .id(id) //
                .type(type) //
                .property("masterDataSourceName", this.getMasterDataSourceName()) //
                .property("slaveDataSourceNameMap", this.slaveDataSourceNameMap()) //
                .setPrimary(this.getPrimary()) //
                .registerTo(registry);
    }

    private void registerShardingAopInterceptor(BeanDefinitionRegistry registry) {
        String id = this.aopInterceptorType().getName();

        new BeanBuilder() //
                .id(id) //
                .type(this.aopInterceptorType()) //
                .property("defaultMasterOnly", this.getDefaultMasterOnly()) //
                .registerTo(registry);
    }

    private void registryShardingAopAdvisor(BeanDefinitionRegistry registry) {
        String aopInterceptorType = this.aopInterceptorType().getName();
        BeanDefinition advisorBean = SpringUtil.getAdvisorBeanDefinitionByAdviceType(registry, aopInterceptorType);
        if (null != advisorBean) {
            log.info("#96 advisorBean=" + advisorBean);
            return;
        }

        // 配置了AOP表达式
        if (null != this.getAopPatterns() && !this.getAopPatterns().trim().isEmpty()) {
            new BeanBuilder().id("budo-sharding-aspectj") //
                    .type(AspectJExpressionPointcutAdvisor.class) //
                    .property("expression", this.getAopPatterns()) //
                    .ref("advice", this.aopInterceptorType().getName())//
                    .registerTo(registry);
            return;
        }

        // 自动
        new BeanBuilder().id("budo-sharding-advisor") //
                .type(BudoAopPointcutAdvisor.class) //
                .ref("advice", this.aopInterceptorType().getName())//
                .registerTo(registry);
    }

    private Class<?> masterSlaveDataSourceFactoryBeanType() {
        if (APACHE_SHARDING_SPHERE) {
            return ApacheShardingMasterSlaveDataSourceFactoryBean.class;
        }

        return MasterSlaveDataSourceFactoryBean.class;
    }

    private Class<?> aopInterceptorType() {
        if (APACHE_SHARDING_SPHERE) {
            return ApacheShardingAnnotationRoutingDataSourceAopInterceptor.class; // 新版
        }

        // 老版
        return AnnotationRoutingDataSourceAopInterceptor.class;
    }
}